package com.adee.java.nio;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.StandardSocketOptions;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;
import java.util.concurrent.TimeUnit;

/*
* javaNIO与linuxNIO对应关系即编程步骤：
* 1. 创建多路复用器（fd容器）
* 2. 创建监听fd
* 3. 把监听fd绑定到端口
* 4. 把监听fd设置为非阻塞（注意：必须在启动监听后设置）
* 5. 监听fd添加到多路复用器中
* 6. 等待多路复用器状态改变
* 7. 遍历多路复用器，对状态改变的fd进行处理
*    7.1 若fd为监听fd，则接收连接并创建新的socketfd，并添加到多路复用器中
*    7.2 若fd可读，则读取数据
 * */
public class SelectorServerTest2 {
    public static void main(String[] args) throws IOException {
        /*
        1. 创建多路复用器（fd容器）
        epoll模型下：epollfd = epoll_create(1);，创建epoll的总fd（epollfd）
        poll模型下：struct pollfd fds[MAXNFDS];，创建fds存放需要监视的socket
        select模型下：fd_set readfdset;，创建bitmap，存放被监视的fd
        select poll epoll 优先选择epoll，但是可以 -D修正
        */
        Selector selector = Selector.open();
        /*
        2. 创建监听fd
        创建监听fd，对应linux-c代码：int sock = socket(AF_INET,SOCK_STREAM,0);
        */
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();

        /*
        3. 把监听fd绑定到端口：bind(sock,(struct sockaddr *)&servaddr,sizeof(servaddr))
        并启动监听：listen(sock,5)
        */
        serverSocketChannel.bind(new InetSocketAddress(9000));
        // 4. 把监听fd设置为非阻塞（注意：必须在启动监听后设置）：fcntl(sockfd, F_SETFL, fcntl(sockfd, F_GETFD, 0)|O_NONBLOCK)
        serverSocketChannel.configureBlocking(false);
        /*
        5. 监听fd添加到多路复用器中
        epoll模型：调用epoll_ctl(epollfd,EPOLL_CTL_ADD,listensock,&ev); 将监听fd(serverSocketChannel)注册到epfd(selector)下
        select/poll模型：调用FD_SET(listensock,&readfdset); 将监听fd(serverSocketChannel)注册到fd_set中
        SelectionKey相当于linux-poll模式中的fds数组中的一个元素，代表了一个channel的状态，一旦注册到某个selector上，
        该SelectionKey的句柄不会改变，其事件状态信息可由selector.select方法改变，也可有用户代码清零重置
        linux-poll模式代码如下：
        struct pollfd fds[MAXNFDS];  // fds存放需要监视的socket。
        fds[clientsock].fd=clientsock;
        fds[clientsock].events=POLLIN; // 关心事件类型为accept、read、write
        fds[clientsock].revents=0; // 已经触发了哪些事件
        */
        SelectionKey selectionKey = serverSocketChannel.register(selector, 0, null);
        selectionKey.interestOps(SelectionKey.OP_ACCEPT);
        selectionKey.attach("hello, i am an attachment Object to selectionKey");
        while (true) {
            // 6. 等待多路复用器状态改变
            int count = selector.select(); // 底层调用 int infds = epoll_wait(epollfd,events,MAXEVENTS,-1);
            if (count == 0) continue;
            Set<SelectionKey> selectionKeys = selector.selectedKeys();
            Iterator<SelectionKey> iterator = selectionKeys.iterator();
            // 7. 遍历多路复用器，对状态改变的fd进行处理
            while (iterator.hasNext()) {
                SelectionKey key = iterator.next();
                iterator.remove();
                if(!key.isValid()) continue;
                // 7.1 若fd为监听fd，则接收连接并创建新的socketfd，并添加到多路复用器中
                if(key.isAcceptable()) {
                    System.out.println("新请求到来");
                    System.out.println("key.channel() == serverSocketChannel: " + (key.channel() == serverSocketChannel)); // true
                    ServerSocketChannel ssc = (ServerSocketChannel)key.channel();
                    SocketChannel socketChannel = ssc.accept();
                    socketChannel.configureBlocking(false);
                    // 缓冲区设置为8字节，socket可读时不执行read，测试接收缓冲区满时，客户端write方法是否阻塞
                    socketChannel.setOption(StandardSocketOptions.SO_RCVBUF, 8);
                    socketChannel.setOption(StandardSocketOptions.SO_SNDBUF, 8);
                    socketChannel.register(selector, SelectionKey.OP_READ); // 把新建立的socketChannel注册到selector上
                    System.out.println("新建socketChannel，并注册到selector上");
                } else if(key.isConnectable()) {
                    System.out.println("连接处理");
                // 7.2 若fd可读，则读取数据
                } else if(key.isReadable()) {
//                    System.out.println("读取处理");
                    SocketChannel socketChannel = (SocketChannel)key.channel();
                    int receiveBufferSize = socketChannel.socket().getReceiveBufferSize();
                    int sendBufferSize = socketChannel.socket().getSendBufferSize();
//                    System.out.printf("receiveBufferSize: %d\n", receiveBufferSize);
//                    System.out.printf("sendBufferSize: %d\n", sendBufferSize);
                    try {
                        TimeUnit.SECONDS.sleep(1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
//                    if(true) continue; // 测试接收缓冲区满，客户端write是否阻塞
                    ByteBuffer buffer = ByteBuffer.allocate(16);
                    int readCount = socketChannel.read(buffer);// 从通道读数据
                    buffer.flip();
                    byte[] bytes = new byte[readCount];
                    buffer.get(bytes);
//                    System.out.println("接受字节数：" + readCount);
//                    String str = "接收到数据：" + new String(bytes);
                    System.out.print(new String(bytes));
//                    socketChannel.write(ByteBuffer.wrap(bytes));  // 向通道写数据
//                    System.out.println("写回数据===：" + str);
                } else if(key.isWritable()) {
                    System.out.println("写入处理");
                }
            }
        }
    }
}
